import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;

import choco.Choco;
import choco.cp.model.CPModel;
import choco.cp.solver.CPSolver;
import choco.kernel.model.constraints.ComponentConstraint;
import choco.kernel.model.variables.integer.IntegerVariable;
import choco.kernel.model.variables.scheduling.TaskVariable;


public class Exercice7 extends CPModel {
	
	private static final String FONDATIONS = "fondations";
	private static final String MURS = "murs";
	private static final String TOITS_PLAFONDS = "toits et plafonds";
	private static final String PLOMBERIE_ELECTRICITE = "plomberie et electricite";
	private static final String SOL = "sol";
	private static final String PEINTURE = "peinture";
	private static final String FACADE = "facade";
	private static final String NETTOYAGE = "nettoyage";
	private static final String MENUISERIE = "menuiserie";
	private static final String FINITIONS = "finitions";
	
	//Le nombre maximal de jours du projet : 39 jours
	private static final int maxJours = 39;
	
	private TaskVariable[] planning;
	private IntegerVariable finDuProjet;
	
	private IntegerVariable[] violations;
	private IntegerVariable sumViolationsCost;
	private int nActivities;
	private int nPrecedences;
	private int index;
	
	HashMap<String, Integer> activitiesTime;//Temps en jours de chaque activité
	HashMap<String, Integer> activitiesID;//Hashmap afin d'attribuer un ID à chaqué activité
	
	public Exercice7() {
		super();
		activitiesTime = new HashMap<String, Integer>();
		activitiesTime.put(FONDATIONS, 5);
		activitiesTime.put(MURS, 13);
		activitiesTime.put(TOITS_PLAFONDS, 7);
		activitiesTime.put(PLOMBERIE_ELECTRICITE, 6);
		activitiesTime.put(SOL, 8);
		activitiesTime.put(PEINTURE, 2);
		activitiesTime.put(FACADE, 13);
		activitiesTime.put(NETTOYAGE, 1);
		activitiesTime.put(MENUISERIE, 3);
		activitiesTime.put(FINITIONS, 2);		
		
		activitiesID = new HashMap<String, Integer>();
		activitiesID.put(FONDATIONS, 0);
		activitiesID.put(MURS, 1);
		activitiesID.put(TOITS_PLAFONDS, 2);
		activitiesID.put(PLOMBERIE_ELECTRICITE, 3);
		activitiesID.put(SOL, 4);
		activitiesID.put(PEINTURE, 5);
		activitiesID.put(FACADE, 6);
		activitiesID.put(NETTOYAGE, 7);
		activitiesID.put(MENUISERIE, 8);
		activitiesID.put(FINITIONS, 9);	
		
		nActivities = activitiesID.size();
		
		planning = new TaskVariable[activitiesTime.size()];		
		for (String activity : activitiesTime.keySet()) {
			planning[activitiesID.get(activity)] = Choco.makeTaskVar(activity, 0, maxJours, activitiesTime.get(activity));
			addVariable(planning[activitiesID.get(activity)]);
		}
		finDuProjet = planning[activitiesID.get(FINITIONS)].end();
		
		nPrecedences = 20;
		index = -1;
		
		violations = Choco.makeIntVarArray("violations", nPrecedences, 0, 1);
		sumViolationsCost = Choco.makeIntVar("somme des violations", 0, nPrecedences);
		
		this.postConstraints();
	}
	
	public void postConstraints() {
		
		// Apres la construction des fondations, les ouvriers peuvent entreprendre le dressage 
		// des murs.
		addSoftPrecedence(FONDATIONS, MURS);

		// Une fois cette tache terminee, nous pouvons commencer le toit, la plomberie et le sol.
		addSoftPrecedence(MURS, TOITS_PLAFONDS);		
		addSoftPrecedence(MURS, PLOMBERIE_ELECTRICITE);
		addSoftPrecedence(MURS, SOL);
		
		//  Les activites de peinture ne peuvent pas etre lancees avant le sechage des plafonds.
		addSoftPrecedence(TOITS_PLAFONDS, PEINTURE);
		
		// la facade et le nettoyage du jardin ne peuvent etre accomplies avant d’avoir termine 
		// la plomberie. 
		addSoftPrecedence(PLOMBERIE_ELECTRICITE, FACADE);
		addSoftPrecedence(PLOMBERIE_ELECTRICITE, NETTOYAGE);
		//  La confection du toit devra  egalement preceder le nettoyage
		addSoftPrecedence(TOITS_PLAFONDS, NETTOYAGE);		
		// La menuiserie survient apres la peinture, la facade et le sol	
		addSoftPrecedence(PEINTURE, MENUISERIE);	
		addSoftPrecedence(FACADE, MENUISERIE);		
		addSoftPrecedence(SOL, MENUISERIE);	
		// Enfin, les activites de finition interviennent une fois le chantier termine
		for (String a: activitiesTime.keySet()) {
			if(a!=FINITIONS) addSoftPrecedence(a, FINITIONS);
		}		
		
		addConstraint(Choco.eq(Choco.sum(violations), sumViolationsCost));

	}
	
	/**
	 * Méthode permettant d'ajouter la contrainte "la tâche task1 a lieu avant la tâche task2"
	 * Violation de precedence d'un jour max, cf SoftLeq
	 * @param task1
	 * @param task2
	 */
	public void addSoftPrecedence(String task1, String task2) {
		IntegerVariable[] variables = Choco.makeIntVarArray("", 3);
		variables[0] = planning[activitiesID.get(task1)].end();
		variables[1] = planning[activitiesID.get(task2)].start();
		this.setIndex(this.getIndex() + 1);
		variables[2] = violations[this.getIndex()];
		addConstraint(new ComponentConstraint(SoftLeqManager.class, null, variables));			
	}

	public CPSolver solve() {
		CPSolver solver = new CPSolver();
		solver.read(this);
		//ChocoLogging.setVerbosity(Verbosity.SOLUTION);
		solver.minimize(solver.getVar(sumViolationsCost), false);
		return solver;
	}
	
	public void print(CPSolver solver) {
		System.out.println("Planification des activités intervenant dans la construction d'une villa");
		Calendar debut = new GregorianCalendar(2012, Calendar.MARCH, 1);
		Calendar fin = new GregorianCalendar(2012, Calendar.MARCH, 1);
		fin.add(Calendar.DAY_OF_MONTH, solver.getVar(finDuProjet).getVal());
		System.out.println("Début du projet : "+debut.getTime());
		System.out.println("Fin du projet au plut tôt : "+fin.getTime());
		System.out.println("Nombre de jours nécessaires : "+solver.getVar(finDuProjet).getVal()+"\n");
		System.out.println("Nombre de violations : "+solver.getVar(sumViolationsCost).getVal()+"\n");

		for (int i = 0; i < planning.length; i++) {
			System.out.println(solver.getVar(planning[i]));
		}
		System.out.println();
	}	

	public int getIndex() {
		return index;
	}

	public void setIndex(int index) {
		this.index = index;
	}

	public int getnPrecedences() {
		return nPrecedences;
	}

	public void setnPrecedences(int nPrecedences) {
		this.nPrecedences = nPrecedences;
	}

	public static void main(String[] args) {

		Exercice7 planif = new Exercice7();
		CPSolver solver = planif.solve();
		planif.print(solver);
	}

}
